package com.tobestronger.shouxiespringframework.aop.framework.autoproxy;

import com.tobestronger.shouxiespringframework.aop.*;
import com.tobestronger.shouxiespringframework.aop.aspectj.AspectJExpressionPointcutAdvisor;
import com.tobestronger.shouxiespringframework.aop.framework.ProxyFactory;
import com.tobestronger.shouxiespringframework.beans.BeansException;
import com.tobestronger.shouxiespringframework.beans.PropertyValues;
import com.tobestronger.shouxiespringframework.beans.factory.BeanFactory;
import com.tobestronger.shouxiespringframework.beans.factory.BeanFactoryAware;
import com.tobestronger.shouxiespringframework.beans.factory.config.InstantiationAwareBeanPostProcessor;
import com.tobestronger.shouxiespringframework.beans.factory.support.DefaultListableBeanFactory;
import org.aopalliance.aop.Advice;
import org.aopalliance.intercept.MethodInterceptor;

import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

/**
 * InstantiationAwareBeanPostProcessor实现
 */
public class DefaultAdvisorAutoProxyCreator implements InstantiationAwareBeanPostProcessor, BeanFactoryAware {

    private DefaultListableBeanFactory beanFactory;

    private final Set<Object> earlyProxyReferences = Collections.synchronizedSet(new HashSet<>());

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        this.beanFactory = (DefaultListableBeanFactory) beanFactory;
    }

    /**
     * 在bean实例化之前执行,如果beanClass需要被代理,则会返回生成的代理对象
     *
     * @param beanClass
     * @param beanName
     * @return
     * @throws BeansException
     */
    @Override
    public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
        return null;
    }

    @Override
    public PropertyValues postProcessPropertyValues(PropertyValues pvs, Object bean, String beanName) throws BeansException {
        return pvs;
    }

    /**
     * 检查是否属于是基础设施类
     *
     * @param beanClass
     * @return
     */
    private boolean isInfrastructureClass(Class<?> beanClass) {
        return Advice.class.isAssignableFrom(beanClass) ||
                Pointcut.class.isAssignableFrom(beanClass) ||
                Advisor.class.isAssignableFrom(beanClass);
    }

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    @Override
    public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
        return true;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {

        if (!earlyProxyReferences.contains(beanName)) {
            return wrapIfNecessary(bean, beanName);
        }

        return bean;
    }

    protected Object wrapIfNecessary(Object bean, String beanName) {

        //基础设施类不做代理
        if (isInfrastructureClass(bean.getClass())){
            return bean;
        }

        //获取所有的AspectJExpressionPointcutAdvisor实现
        Collection<AspectJExpressionPointcutAdvisor> advisors =
                beanFactory.getBeansOfType(AspectJExpressionPointcutAdvisor.class).values();

        //遍历所有 advisors
        for (AspectJExpressionPointcutAdvisor advisor : advisors) {

            //检查 当前advisor 是否要对当前 beanClass 生成代理
            ClassFilter classFilter = advisor.getPointcut().getClassFilter();
            if (!classFilter.matches(bean.getClass())){
                //当前的不需要,那就跳过,继续检查下一个advisor
                continue;
            }

            //当前beanClass需要采用当前advisor做代理
            //组装AdvisedSupport
            AdvisedSupport advisedSupport = new AdvisedSupport();
            advisedSupport.setTargetSource(new TargetSource(bean));
            advisedSupport.setMethodInterceptor((MethodInterceptor) advisor.getAdvice());
            advisedSupport.setMethodMatcher(advisor.getPointcut().getMethodMatcher());
            advisedSupport.setProxyTargetClass(true);

            //生成代理对象并返回
            return new ProxyFactory(advisedSupport).getProxy();

        }

        return bean;
    }

    @Override
    public Object getEarlyBeanReference(Object bean, String beanName) {
        earlyProxyReferences.add(beanName);
        return wrapIfNecessary(bean, beanName);
    }

    
}
